﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Secondary_File_System.Model;

namespace Secondary_File_System.lib
{
    class FileSystem
    {
        //Configuration
        public const string diskFilePath = "./vDisk.dat";
        public const int K = 1024;//1K=1024
        public const int userCount = 10;
        public const int directoryCount = 10;
        public const int inodeCount = 1000;
        public const int blockCount = 100000;
        public static Encoding systemEncoding = Encoding.UTF8;

        //Start Index, calculated by configuration.
        //YOU MUST MODIFY THESE CONSTANTS IF YOU CHANGED THE CONFIGURATION ABOVE
        const int userBMPStartIndex = 0;
        const int directoryBMPStartIndex = 1 * K;
        const int inodeBMPStartIndex = 2 * K;
        const int blockBMPStartIndex = 3 * K;
        const int superBlockStartIndex = 103 * K;
        const int userStartIndex = 104 * K;
        const int directoryStartIndex = 114 * K;
        const int inodeStartIndex = 124 * K;
        const int blockStartIndex = 1124 * K;

        //Properties
        protected User _currentUser;
        public User currentUser
        {
            get
            {
                return this._currentUser;
            }
        }
        /// <summary>
        /// 当前正在编辑的文件
        /// </summary>
        protected string currentFileName { get; set; }
        /// <summary>
        /// 是否已经打开了文件正在编辑
        /// </summary>
        public bool editingFile { get { return currentFileName != null; } }

        //cache
        protected bool[] userBMP = new bool[userCount];
        protected bool[] directoryBMP = new bool[directoryCount];
        protected bool[] inodeBMP = new bool[inodeCount];
        protected bool[] blockBMP = new bool[blockCount];
        protected SuperBlock superBlock = new SuperBlock();
        protected Dictionary<string, User> dicUserCache = new Dictionary<string, User>(); //Dictionary<string username, User>
        protected Dictionary<int, Model.Directory> dicDirectoryCache = new Dictionary<int, Model.Directory>(); //Dictionary<int Index, Directory>

        public FileSystem()
        {
            _currentUser = null;
            if (!File.Exists(diskFilePath))
            {
                Console.WriteLine("正在创建虚拟磁盘...");
                format();
                Console.WriteLine("* * * * * * * * * * * * * * * * * * * * * * * *");
                Console.WriteLine("\t\t创建成功！\n");
                Console.WriteLine("\t\t用户名：admin\n\t\t密码：admin");
                Console.WriteLine("* * * * * * * * * * * * * * * * * * * * * * * *");
            }
            else init();
        }

        //Actions
        /// <summary>
        /// 从虚拟磁盘初始化分区
        /// </summary>
        public void init()
        {
            FileStream fs = new FileStream(diskFilePath, FileMode.Open, FileAccess.Read);
            //userBMP
            fs.Position = userBMPStartIndex;
            for (int i = 0; i < userCount; i++)
            {
                userBMP[i] = fs.ReadByte() == 1;
            }
            //directoryBMP
            fs.Position = directoryBMPStartIndex;
            for (int i = 0; i < directoryCount; i++)
            {
                directoryBMP[i] = fs.ReadByte() == 1;
            }
            //inodeBMP
            fs.Position = inodeBMPStartIndex;
            for (int i = 0; i < inodeCount; i++)
            {
                inodeBMP[i] = fs.ReadByte() == 1;
            }
            //blockBMP
            fs.Position = blockBMPStartIndex;
            for (int i = 0; i < blockCount; i++)
            {
                blockBMP[i] = fs.ReadByte() == 1;
            }
            //Super Block
            fs.Position = superBlockStartIndex;
            this.superBlock = SuperBlock.readFromStream(ref fs);
            //User Cache
            for (int userIndex = 0; userIndex < userCount; userIndex++)
            {
                if (userBMP[userIndex] == true)
                {
                    fs.Position = userStartIndex + userIndex * K;
                    User cachedUserInfo = User.readFromStream(ref fs);
                    this.dicUserCache.Add(cachedUserInfo.username, cachedUserInfo);
                }
            }
            //Directory Cache
            for (int directoryIndex = 0; directoryIndex < directoryCount; directoryIndex++)
            {
                if (directoryBMP[directoryIndex] == true)
                {
                    fs.Position = directoryStartIndex + directoryIndex * K;
                    Model.Directory cachedDirectory = Model.Directory.readFromStream(ref fs);
                    this.dicDirectoryCache.Add(cachedDirectory.index, cachedDirectory);
                }
            }
            //Inode Cache
            foreach (Model.Directory directory in dicDirectoryCache.Values)
            {
                foreach (int InodeIndex in directory.fileInodeIndex)
                {
                    fs.Position = inodeStartIndex + InodeIndex * K;
                    Inode cachedInode = Inode.readFromStream(ref fs);
                    directory.dicInodeCache.Add(cachedInode.fileName, cachedInode);
                }
            }
            fs.Close();
        }

        /// <summary>
        /// 格式化虚拟磁盘
        /// </summary>
        public void format()
        {
            FileStream fs = new FileStream(diskFilePath, FileMode.Create, FileAccess.Write);
            byte[] buffer;
            //user, directory, inode各自的个数都小于1K，所以可以共用一个初始化buffer，且这个buffer大小为1K（一个扇区）
            //userBMP
            buffer = new byte[K];
            for (int i = 0; i < buffer.Length; i++)
                buffer[i] = 0;
            Utility.writeBytesToStream(buffer, ref fs);
            //directoryBMP
            Utility.writeBytesToStream(buffer, ref fs);
            //inodeBMP
            Utility.writeBytesToStream(buffer, ref fs);
            //blockBMP
            //block个数为100000个，所以取100K
            buffer = new byte[100 * K];
            for (long i = 0; i < buffer.Length; i++)
                buffer[i] = 0;
            Utility.writeBytesToStream(buffer, ref fs);
            //Super Block
            new SuperBlock().save(ref fs);
            //剩余部分不管，直接移到末尾填入一个字符占位即可
            int remainingBytes = userCount * K + directoryCount * K + inodeCount * K + blockCount * K;
            fs.Seek(remainingBytes - 1, SeekOrigin.Current);
            fs.WriteByte(new byte());
            fs.Flush();
            fs.Close();
            this.init();
            this.createUser("admin", "admin");
        }

        /// <summary>
        /// 登录用户
        /// </summary>
        /// <param name="username">用户名</param>
        /// <param name="password">密码</param>
        /// <returns></returns>
        public bool login(string username, string password)
        {
            if (!dicUserCache.Keys.Contains(username)) return false;
            if (dicUserCache[username].password != password) return false;
            this._currentUser = dicUserCache[username];
            return true;
        }

        /// <summary>
        /// 创建用户
        /// </summary>
        /// <param name="username">用户名</param>
        /// <param name="password">密码</param>
        /// <returns></returns>
        public bool createUser(string username, string password)
        {
            if (dicUserCache.Keys.Contains(username)) return false;
            if (superBlock.freeUser < 1) return false;
            for (int index = 0; index < userCount; index++)
            {
                if (userBMP[index] == false)
                {
                    FileStream fs = new FileStream(diskFilePath, FileMode.Open, FileAccess.ReadWrite);
                    //Super Block
                    this.superBlock.freeUser -= 1;
                    fs.Position = superBlockStartIndex;
                    this.superBlock.save(ref fs);
                    //userBMP
                    userBMP[index] = true;
                    fs.Position = userBMPStartIndex + index;
                    fs.WriteByte((byte)1);
                    fs.Flush();
                    //directoryBMP
                    directoryBMP[index] = true;
                    fs.Position = directoryBMPStartIndex + index;
                    fs.WriteByte((byte)1);
                    fs.Flush();
                    //user
                    fs.Position = userStartIndex + index * K;
                    User user = new User();
                    user.index = index;
                    user.username = username;
                    user.password = password;
                    user.directoryIndex = index;
                    user.save(ref fs);
                    dicUserCache.Add(username, user);
                    //directory
                    fs.Position = directoryStartIndex + user.directoryIndex * K;
                    Model.Directory directory = new Model.Directory();
                    directory.index = user.directoryIndex;
                    directory.fileCount = 0;
                    directory.fileInodeIndex = new int[0];
                    directory.save(ref fs);
                    dicDirectoryCache.Add(directory.index, directory);
                    fs.Close();
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 磁盘信息
        /// </summary>
        public void info()
        {
            Console.WriteLine("* * * * * * * * * * * * * * * * * * * * * * * *");
            Console.WriteLine("\t\t磁盘总空间：{0}K\n\t\t已用空间：{1}K\n\t\t剩余空间：{2}K\n\t\t用户数：{3}\n\t\t当前用户文件数：{4}", blockCount, (blockCount - superBlock.freeBlock), superBlock.freeBlock, dicUserCache.Count, dicDirectoryCache[currentUser.directoryIndex].fileCount);
            Console.WriteLine("* * * * * * * * * * * * * * * * * * * * * * * *");
        }

        /// <summary>
        /// 列出当前用户的文件信息
        /// </summary>
        public void dir()
        {
            if (currentUser == null) return;
            Console.WriteLine(currentUser.username + "的文件目录：");
            Console.WriteLine(String.Format("{0,-18}{1,-8}{2,-8}{3,-8}{4}", "修改日期", "物理地址", "文件长度", "读写限制", "文件名"));
            foreach (Inode inode in dicDirectoryCache[currentUser.directoryIndex].dicInodeCache.Values)
            {
                string authority = inode.readOnly ? "只读" : "读写";
                Console.WriteLine(String.Format("{0,-23}{1,-11:00000}{2,-14}{3,-8}{4}", inode.showUpdatedTime(), inode.firstBlockIndex, inode.fileLength, authority, inode.fileName));
            }
        }

        /// <summary>
        /// 创建文件
        /// </summary>
        /// <param name="fileName">文件名</param>
        /// <param name="readOnly">读写权限（是否只读）</param>
        /// <returns></returns>
        public bool createFile(string fileName, bool readOnly)
        {
            if (currentUser == null) return false;
            if (superBlock.freeInode < 1) return false;
            if (dicDirectoryCache[currentUser.directoryIndex].dicInodeCache.Keys.Contains(fileName)) return false;
            for (int inodeIndex = 0; inodeIndex < inodeCount; inodeIndex++)
            {
                if (inodeBMP[inodeIndex] == false)
                {
                    FileStream fs = new FileStream(diskFilePath, FileMode.Open, FileAccess.ReadWrite);
                    //inodeBMP
                    inodeBMP[inodeIndex] = true;
                    fs.Position = inodeBMPStartIndex + inodeIndex;
                    fs.WriteByte((byte)1);
                    fs.Flush();
                    //blockBMP
                    blockBMP[inodeIndex * 100] = true;
                    fs.Position = blockBMPStartIndex + inodeIndex * 100;
                    fs.WriteByte((byte)1);
                    fs.Flush();
                    //inode
                    fs.Position = inodeStartIndex + inodeIndex * K;
                    Inode inode = new Inode();
                    inode.index = inodeIndex;
                    inode.updatedTime = DateTime.Now;
                    inode.readOnly = readOnly;
                    inode.firstBlockIndex = inodeIndex * 100;
                    inode.fileName = fileName;
                    inode.fileLength = 0;
                    inode.save(ref fs);
                    //更新directory信息
                    fs.Position = directoryStartIndex + currentUser.directoryIndex * K;
                    dicDirectoryCache[currentUser.directoryIndex].addFile(inode);
                    dicDirectoryCache[currentUser.directoryIndex].save(ref fs);
                    //Super Block
                    this.superBlock.freeInode -= 1;
                    this.superBlock.freeBlock -= 1;
                    fs.Position = superBlockStartIndex;
                    this.superBlock.save(ref fs);
                    fs.Close();
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 删除文件
        /// </summary>
        /// <param name="fileName">文件名</param>
        /// <returns></returns>
        public bool deleteFile(string fileName)
        {
            if (currentUser == null) return false;
            if (!dicDirectoryCache[currentUser.directoryIndex].dicInodeCache.Keys.Contains(fileName)) return false;
            Inode inode = dicDirectoryCache[currentUser.directoryIndex].dicInodeCache[fileName];
            FileStream fs = new FileStream(diskFilePath, FileMode.Open, FileAccess.ReadWrite);
            //inodeBMP
            inodeBMP[inode.index] = false;
            fs.Position = inodeBMPStartIndex + inode.index;
            fs.WriteByte((byte)0);
            fs.Flush();
            //blockBMP
            fs.Position = blockBMPStartIndex + inode.firstBlockIndex;
            for (int i = 0; i < inode.fileLength / K + 1; i++)
            {
                blockBMP[inode.firstBlockIndex + i] = false;
                fs.WriteByte((byte)0);
            }
            fs.Flush();
            //更新directory信息
            fs.Position = directoryStartIndex + currentUser.directoryIndex * K;
            dicDirectoryCache[currentUser.directoryIndex].deleteFile(inode);
            dicDirectoryCache[currentUser.directoryIndex].save(ref fs);
            //Super Block
            fs.Position = superBlockStartIndex;
            this.superBlock.freeBlock += inode.fileLength / K + 1;
            this.superBlock.freeInode += 1;
            this.superBlock.save(ref fs);
            fs.Close();
            return true;
        }

        /// <summary>
        /// 打开文件
        /// </summary>
        /// <param name="fileName">文件名</param>
        /// <returns></returns>
        public bool openFile(string fileName)
        {
            if (currentUser == null) return false;
            if (!dicDirectoryCache[currentUser.directoryIndex].dicInodeCache.Keys.Contains(fileName)) return false;
            Inode inode = dicDirectoryCache[currentUser.directoryIndex].dicInodeCache[fileName];
            this.currentFileName = inode.fileName;
            return true;
        }

        /// <summary>
        /// 关闭已打开的文件
        /// </summary>
        /// <returns></returns>
        public bool closeFile()
        {
            if (currentUser == null) return false;
            if (this.currentFileName == null) return false;
            this.currentFileName = null;
            return true;
        }

        /// <summary>
        /// 向已打开的文件写入内容
        /// </summary>
        /// <param name="content">待写入的内容</param>
        /// <returns></returns>
        public bool writeFile(string content)
        {
            if (currentUser == null) return false;
            if (this.currentFileName == null) return false;
            if (dicDirectoryCache[currentUser.directoryIndex].dicInodeCache[this.currentFileName].readOnly == true) return false;
            byte[] buffer = systemEncoding.GetBytes(content);
            if (buffer.Length > 100 * K) return false;
            //block
            FileStream fs = new FileStream(diskFilePath, FileMode.Open, FileAccess.ReadWrite);
            fs.Position = blockStartIndex + dicDirectoryCache[currentUser.directoryIndex].dicInodeCache[this.currentFileName].firstBlockIndex * K;
            Utility.writeBytesToStream(buffer, ref fs);
            //super
            fs.Position = superBlockStartIndex;
            this.superBlock.freeBlock += (buffer.Length / K + 1) - (dicDirectoryCache[currentUser.directoryIndex].dicInodeCache[this.currentFileName].fileLength / K + 1);
            this.superBlock.save(ref fs);
            //inode
            fs.Position = inodeStartIndex + dicDirectoryCache[currentUser.directoryIndex].dicInodeCache[this.currentFileName].index * K;
            dicDirectoryCache[currentUser.directoryIndex].dicInodeCache[this.currentFileName].fileLength = buffer.Length;
            dicDirectoryCache[currentUser.directoryIndex].dicInodeCache[this.currentFileName].save(ref fs);
            fs.Close();
            return true;
        }

        /// <summary>
        /// 读出已打开文件的内容
        /// </summary>
        /// <returns></returns>
        public string readFile()
        {
            if (currentUser == null) return "【操作失败】SECURITY ERROR";
            if (this.currentFileName == null) return "【操作失败】CRITICAL ERROR";
            FileStream fs = new FileStream(diskFilePath, FileMode.Open, FileAccess.ReadWrite);
            fs.Position = blockStartIndex + dicDirectoryCache[currentUser.directoryIndex].dicInodeCache[this.currentFileName].firstBlockIndex * K;
            string content = Utility.readStringFromStream(ref fs, dicDirectoryCache[currentUser.directoryIndex].dicInodeCache[this.currentFileName].fileLength);
            fs.Close();
            return content;
        }

        /// <summary>
        /// 复制文件
        /// </summary>
        /// <param name="sourceFileName">源文件</param>
        /// <param name="destinateFileName">目标文件</param>
        /// <returns></returns>
        public bool copyFile(string sourceFileName, string destinateFileName)
        {
            if (!this.openFile(sourceFileName)) return false;
            string content = this.readFile();
            if (content.Contains("【操作失败】")) return false;
            if (!this.closeFile()) return false;
            if (!this.createFile(destinateFileName, false)) return false;
            if (!this.openFile(destinateFileName)) return false;
            if (!this.writeFile(content)) return false;
            this.closeFile();
            return true;
        }

        /// <summary>
        /// 更改文件的读写权限
        /// </summary>
        /// <param name="readOnly">读写权限（是否只读）</param>
        /// <returns></returns>
        public bool changeAuthority(bool readOnly)
        {
            if (currentUser == null) return false;
            if (this.currentFileName == null) return false;
            FileStream fs = new FileStream(diskFilePath, FileMode.Open, FileAccess.ReadWrite);
            fs.Position = inodeStartIndex + dicDirectoryCache[currentUser.directoryIndex].dicInodeCache[this.currentFileName].index * K;
            dicDirectoryCache[currentUser.directoryIndex].dicInodeCache[this.currentFileName].readOnly = readOnly;
            dicDirectoryCache[currentUser.directoryIndex].dicInodeCache[this.currentFileName].save(ref fs);
            fs.Close();
            return true;
        }
    }
}
